// prevent execution of jamal if included more than once
if(typeof window.jamal == "undefined") {

/* SVN FILE: $Id: jamal.js 28 2007-07-03 21:00:09Z teemow $ */
/**
 * This is the Jamal core. Heavily inspired by jQuery's architecture. 
 *
 * To quote Dave Cardwell: 
 * Built on the shoulders of giants:
 *   * John Resig      - http://jquery.com/
 *
 * jQuery is required
 *
 * Jamal :  Javascript MVC Assembly Layout <http://jamal.moagil.de/>
 * Copyright (c)    2006, Timo Derstappen <http://teemow.com/>
 *
 * Licensed under The MIT License
 * Redistributions of files must retain the above copyright notice.
 *
 * @filesource
 * @copyright        Copyright (c) 2006, Timo Derstappen
 * @link            
 * @package          jamal
 * @subpackage       jamal.core
 * @since            Jamal v 0.1
 * @version          $Revision: 28 $
 * @modifiedby       $LastChangedBy: teemow $
 * @lastmodified     $Date: 2007-07-03 23:00:09 +0200 (Di, 03 Jul 2007) $
 * @license          http://www.opensource.org/licenses/mit-license.php The MIT License
 */

/**
 * Create a new jamal Object
 *
 * @constructor
 * @private
 * @name jamal
 * @cat core
 */
var jamal = function() {
    // If the context is global, return a new object
    if (window == this) {
        return new jamal();
    }
    
    return this.configure();
};

/**
 * Create the jamal core prototype
 *
 * @public
 * @name jamal
 * @cat core
 */
jamal.fn = jamal.prototype = {
    /* Properties */

    /**
     * The current version of jamal.
     *
     * @private
     * @property
     * @name version
     * @type String
     * @cat core
     */
    version: '0.4',

    /**
     * Defines the root element with the jamal configuration class. This is 
     * necessary due to performance. jQuery is a lot faster in finding classes
     * when it knows the holding element.
     *
     * @private
     * @property
     * @name root
     * @type String
     * @cat core
     */
    root: 'body',

    /**
     * Name of the current controller.
     *
     * @public
     * @property
     * @name name
     * @type String
     * @cat core
     */
    name: '',

    /**
     * Name of the current action.
     *
     * @public
     * @property
     * @name action
     * @type String
     * @cat core
     */
    action: '',

    /**
     * Current controller object.
     *
     * @public
     * @property
     * @name controller
     * @type Object
     * @cat core
     */
    current: {},

    /**
     * Map of all available models.
     *
     * @public
     * @property
     * @name m
     * @type Map
     * @cat core
     */
    m: {},

    /**
     * Map of all available views.
     *
     * @public
     * @property
     * @name v
     * @type Map
     * @cat core
     */
    v: {},

    /**
     * Map of all available controllers.
     *
     * @public
     * @property
     * @name c
     * @type Map
     * @cat core
     */
    c: {},

    /**
     * Jamal configuration passed from the root elements class
     *
     * @public
     * @property
     * @name config
     * @type Object
     * @cat core
     */
    config: {},

    /**
     * Debug flag to give more information about jamal in the console.
     *
     * @private
     * @property
     * @name debug
     * @type Boolean
     * @cat core
     */
    debug: false,

    /* Methods */
    
    /**
     * Method description
     *
     * @example jamal.start();
     * @result jamal.current == [ new Controller ]
     *
     * @public
     * @name start
     * @type jamal
     * @cat core
     */
    start: function() {
        this.log('Starting the Jamal application (Version: '+this.version+')...');
        this.log('Controller: ' + this.name);
        this.log('Action: ' + this.action);
        if (this.debug === true) {
            window.console.time('Timing');
        }
        var started = this.load();
        if (this.debug === true) {
            window.console.timeEnd('Timing');
        }
        if (jQuery.browser.mozilla) {
            this.log('Jamal size: '+this.toSource().length+' Chars');
        }
        
        // capture errors
        jQuery(window).error(function(message, file, line) {
            var e = {'name':'window.onerror',
                     'message':message,
                     'file':file,
                     'line':line,
                     'stack':''
                    };
            jamal.fn.error('Window error captured!', e);
            return true;
        });
                    
        return started;
    },

    /**
     * Log messages on the browser console. Firebug is recommended.
     *
     * @example jamal.log('current controller: ' + this.controller);
     *
     * @public
     * @name start
     * @type debug
     * @param String message The message to be displayed on the console
     * @param String message (optional) More messages to be displayed on the console
     * @cat log
     */
    log: function(message) {
        if (this.debug === true) {
            var log = '';
            for (var i=0; i<arguments.length; i++) {
                log += arguments[i];
                if (i !== (arguments.length-1)) {
                    log += ', ';
                }
            }
            window.console.log(log);
        }
    },

    /**
     * Log jamal errors to the console
     *
     * @example jamal.error('Controller not found!');
     *
     * @public
     * @name start
     * @type debug
     * @param String message Error message to be displayed on the console
     * @param Object e (optional) Error object to display the original error
     * @cat log
     */
    error: function(message) {
        if (this.debug === true) {
            window.console.error('Jamal Error: '+message);
            if (arguments.length>1) {
                e = arguments[1];
                this.log(e.name+': '+e.message);
                this.dir(e);
                this.log('Stack: ' + e.stack);
            }
        }
    },
    
    /**
     * Log objects to the console
     *
     * @example jamal.dir(obj);
     * @result [ { prop1: val1, prop2: val2 } ]
     *
     * @public
     * @name dir
     * @type debug
     * @param Object obj The object which should be logged on the console.
     * @cat log
     */
    dir: function(obj) {
        if (this.debug === true) {
            window.console.dir(obj);
        }
    },

    /**
     * Try to configure jamal
     *
     * Currently it is expected that there is a dom element with metadata
     * attached. This data is read via jQuery's metadata plugin.
     *
     * This makes it very easy to use jamal with e.g. CakePHP. Just add
     * <body class="jamal {controller:'<?php echo $this->name; ?>',action:'<?php echo $this->action; ?>'}"> 
     * to your default layout. Now you only need to create and include the 
     * corresponding js files.
     *
     * @example jamal.configure();
     * @before <body class="jamal {controller:'Tests',action:'index'}">
     * @result [ jamal.controller = 'Tests', jamal.action = 'index' ]
     *
     * @private
     * @name configure
     * @type jamal
     * @cat core
     */
    configure: function() {
        try {
            data = jQuery(this.root+'.jamal').data();
        } catch(e) {
            this.debug = true;
            this.error('jQuery Metadata Plugin failed to read the configuration. '+
                       'Probably there is no class="jamal {controller:\'example\',action:\'index\'}" in your markup!', e);
        }
        
        if (typeof(data) !== 'object') {
            this.debug = true;
            this.error('No configuration found!');
            return false;
        } else {
            this.config = data;
            this.name = data.controller;
            this.action = data.action;
            this.debug = data.debug;
            return true;
        }
    },

    /**
     * Try to load the controller action 
     *
     * @example jamal.load();
     *
     * @public
     * @name load
     * @type mvc
     * @cat core
     */
    load: function () {
        var loaded = false;
        if (typeof this.c[this.name] === 'object') {
            
            // controller
            try {
                this.current = this.c[this.name];
            } catch(e) {
                this.error('Controller error!', e);
            }
            
            // components
            if(this.current.components) {
                for(i in this.current.components) {
                    try {
                        this[this.current.components[i]]();
                    } catch(e) {
                        this.error(this.current.components[i]+' component error!', e);
                    }
                }
            }
            
            // action
            if (typeof this.c[this.name][this.action] === 'function') {
                try {
                    this.current[this.action]();
                    loaded = true;
                } catch(e) {
                    this.error('Action couldn\'t be started!', e);
                }
            } else {
                this.log('Action not found!');
            }
        } else {
            this.log('Controller not found!');
        }
        return loaded;
    },

    /**
     * Run this function to give control of the $j variable back
     * to whichever library first implemented it. This helps to make 
     * sure that jamal doesn't conflict with the $j object
     * of other libraries.
     *
     * By using this function, you will only be able to access jamal
     * using the 'jamal' variable. For example, where you used to do
     * $j.json("/example/action"), you now must do jamal.json("/example/action").
     *
     * @example jamal.noConflict();
     * // Do something with jamal
     * jamal.json("/example/action");
     * @desc Maps the original object that was referenced by $j back to $j
     *
     * @name noConflict
     * @type undefined
     * @cat core 
     */
    noConflict: function() {
        if (jamal._$) {
            $j = jamal._$j;
        }
        return jamal;
    }
};

/**
 * Extend one object with one or more others, returning the original,
 * modified, object. This is a great utility for simple inheritance.
 * 
 * @example var settings = { validate: false, limit: 5, name: "foo" };
 * var options = { validate: true, name: "bar" };
 * jamal.extend(settings, options);
 * @result settings == { validate: true, limit: 5, name: "bar" }
 * @desc Merge settings and options, modifying settings
 *
 * @example var defaults = { validate: false, limit: 5, name: "foo" };
 * var options = { validate: true, name: "bar" };
 * var settings = jamal.extend({}, defaults, options);
 * @result settings == { validate: true, limit: 5, name: "bar" }
 * @desc Merge defaults and options, without modifying the defaults
 *
 * @name $.extend
 * @param Object target The object to extend
 * @param Object prop1 The object that will be merged into the first.
 * @param Object propN (optional) More objects to merge into the first
 * @type Object
 * @cat JavaScript
 */
jamal.extend = jamal.fn.extend = function() {
    // copy reference to target object
    var target = arguments[0], a = 1;

    // extend jamal itself if only one argument is passed
    if (arguments.length == 1) {
        target = this;
        a = 0;
    }
    var prop;
    while ((prop = arguments[a++]) != null) {
        // Extend the base object
        for (var i in prop) {
            target[i] = prop[i];
        }
    }

    // Return the modified object
    return target;
};

/* SVN FILE: $Id: jamal.js 22 2007-06-28 00:09:37Z teemow $ */
/**
 * To quote Dave Cardwell: 
 * Built on the shoulders of giants:
 *   * John Resig      - http://jquery.com/
 *
 * Jamal :  Javascript MVC Assembly Layout <http://jamal.moagil.de/>
 * Copyright (c)    2006, Timo Derstappen <http://teemow.com/>
 *
 * Licensed under The MIT License
 * Redistributions of files must retain the above copyright notice.
 *
 * @filesource
 * @copyright        Copyright (c) 2006, Timo Derstappen
 * @link            
 * @package          jamal
 * @subpackage       jamal.core
 * @since            Jamal v 0.1
 * @version          $Revision: 28 $
 * @modifiedby       $LastChangedBy: teemow $
 * @lastmodified     $Date: 2007-07-03 23:00:09 +0200 (Di, 03 Jul 2007) $
 * @license          http://www.opensource.org/licenses/mit-license.php The MIT License
 */

/**
 * Jamal controller constructor
 *
 * @name jamal.controller
 * @type Object
 * @param String controller Name of the constructed controller
 * @cat controller
 */
/**
 * Inherit a controller from the jamal app controller
 * 
 * @example $j.c({Foos:{
 *     index: function(){
 *         alert('hello world');
 *     }
 * });
 * @desc Merge controller into jamal.c map and inherit everything from jamal.controller
 *
 * @name $j.m
 * @param Object controller The controller that will be merged into the controller map.
 * @type Object
 * @cat core
 * @todo merge jamal.c and jamal.controller with function overloading
 */
jamal.c = jamal.fn.c = function(controller) {
    if(typeof controller === 'object') {
        var inherited;
        for(var i in controller) {
            inherited = new jamal.fn.c(i);
            jamal.extend(inherited, controller[i]);
            
            // add model
            var m = i.substr(0, i.length-1).replace(/ie$/, 'y'); // name is singular
            if(jamal.fn.m[m]) {
                inherited.m = jamal.fn.m[m];
            } else {
                // if no model create one
                inherited.m = jamal.fn.m[m] = new jamal.fn.m(m);
            }
            
            // add view
            if(jamal.fn.v[i]) {
                inherited.v = jamal.fn.v[i];
            } else {
                // if no view create one
                inherited.v = jamal.fn.v[i] = new jamal.fn.v(i);
            }
            
            controller[i] = inherited;
        }
        jamal.extend(jamal.fn.c, controller);
    } else {
        this.name = controller;
    }
};

jamal.fn.extend(jamal.fn.c.prototype, {
    /**
     * (Re-)Initialize a controller
     *
     * @example jamal.controller.init()
     * @desc initializes the current controller action
     *
     * @example filter = $('#list');
     * jamal.controller.init(filter)
     * @desc initializes the current controller action but events are only 
     * bind to elements in #list
     * 
     * @public
     * @name init
     * @param Object filter Dom element which should be reinitialized
     * @cat controller
     */
    init: function(filter){
        jamal.current[jamal.action](filter);
    }
});

/* SVN FILE: $Id: jamal.js 22 2007-06-28 00:09:37Z teemow $ */
/**
 * To quote Dave Cardwell: 
 * Built on the shoulders of giants:
 *   * John Resig      - http://jquery.com/
 *
 * Jamal :  Javascript MVC Assembly Layout <http://jamal.moagil.de/>
 * Copyright (c)    2006, Timo Derstappen <http://teemow.com/>
 *
 * Licensed under The MIT License
 * Redistributions of files must retain the above copyright notice.
 *
 * @filesource
 * @copyright        Copyright (c) 2006, Timo Derstappen
 * @link            
 * @package          jamal
 * @subpackage       jamal.core
 * @since            Jamal v 0.1
 * @version          $Revision: 28 $
 * @modifiedby       $LastChangedBy: teemow $
 * @lastmodified     $Date: 2007-07-03 23:00:09 +0200 (Di, 03 Jul 2007) $
 * @license          http://www.opensource.org/licenses/mit-license.php The MIT License
 */

/**
 * Jamal model constructor
 *
 * @name jamal.model
 * @type Object
 * @param String model Name of the constructed model
 * @cat core
 */
/**
 * Inherit a model from the jamal app model.
 * 
 * @example $j.m({Foo:{
 *     getBar: function(){
 *         $j.json('/test/', function(response){
 *         });
 *     }
 * });
 * @desc Merge model into jamal.m map and inherit everything from jamal.model
 *
 * @name $j.m
 * @param Object model The model that will be merged into the model map.
 * @type Object
 * @cat core
 * @todo merge jamal.m and jamal.model with function overloading
 */
jamal.m = jamal.fn.m = function(model) {
    if(typeof model === 'object') {
        var inherited;
        for (var i in model) {
            // get the jamal model
            inherited = new jamal.fn.m(i);
            
            // inherit the new model
            jamal.extend(inherited, model[i]);
            model[i] = inherited;
        }
        jamal.extend(jamal.fn.m, model);
    } else {
        this.name = model;
    }
};

jamal.fn.extend(jamal.fn.m.prototype, {
    /**
     * A wrapper for jQuerys getJSON
     *
     * We need a wrapper here to add the global callback. Please use jamal.json
     * in your controllers/models.
     *
     * @example jamal.model.json('/test/', 
     *   function(response) {
     *     jamal.dir(response.data);
     *   });
     *
     * @public
     * @name json
     * @param String url The URL of the page to load.
     * @param Function callback A function to be executed whenever the data is loaded.
     * @cat model
     * @todo this method should be moved to a general jamal model class
     */
    json: function(url, callback) {
        model = this;
        jQuery.getJSON(url, null, function(response) {
            model.callback(response, callback);
        });
    },

    /**
     * A general callback for all the model
     *
     * Jamal expects a JSON response like 
     * { 
     *   data: {}
     * }
     *
     * @example jamal.model.callback(response, 
     *   function(response){
     *     jamal.dir(response.data)
     *   });
     *
     * @public
     * @name callback
     * @param Object response JSON response from the server.
     * @param Function callback A function to be executed whenever the data is loaded.
     * @cat model
     */
    callback: function(response, callback){
        if (callback) {
            callback(response);
        }
    }
});

/* SVN FILE: $Id: jamal.js 22 2007-06-28 00:09:37Z teemow $ */
/**
 * To quote Dave Cardwell: 
 * Built on the shoulders of giants:
 *   * John Resig      - http://jquery.com/
 *
 * Jamal :  Javascript MVC Assembly Layout <http://jamal.moagil.de/>
 * Copyright (c)    2006, Timo Derstappen <http://teemow.com/>
 *
 * Licensed under The MIT License
 * Redistributions of files must retain the above copyright notice.
 *
 * @filesource
 * @copyright        Copyright (c) 2006, Timo Derstappen
 * @link            
 * @package          jamal
 * @subpackage       jamal.core
 * @since            Jamal v 0.1
 * @version          $Revision: 28 $
 * @modifiedby       $LastChangedBy: teemow $
 * @lastmodified     $Date: 2007-07-03 23:00:09 +0200 (Di, 03 Jul 2007) $
 * @license          http://www.opensource.org/licenses/mit-license.php The MIT License
 */

/**
 * Jamal view constructor
 *
 * @name jamal.view
 * @type Object
 * @param String view Name of the constructed view
 * @cat view
 */
/**
 * Inherit a view from the jamal app view.
 * 
 * @example $j.v({Foos:{
 *     removeMessage: function(){
 *         $('div.message').remove();
 *     }
 * });
 * @desc Merge view into jamal.v map and inherit everything from jamal.view
 *
 * @name $j.v
 * @param Object view The view that will be merged into the view map.
 * @type Object
 * @cat core
 * @todo merge jamal.v and jamal.view with function overloading
 */
jamal.v = jamal.fn.v = function(view) {
    if(typeof view === 'object') {
        var inherited;
        for (var i in view) {
            inherited = new jamal.fn.v(i);
            jamal.extend(inherited, view[i]);
            view[i] = inherited;
        }
        jamal.extend(jamal.fn.v, view);
    } else {
        this.name = view;
    }
};

jamal.fn.extend(jamal.fn.v.prototype, {
    /**
     * Decode HTML entities
     *
     * @example jamal.view.decode_html()
     * 
     * @public
     * @name decode_html
     * @type jamal
     * @cat view
     */
    decode_html: function(str) {
        if (typeof str === 'string') {
            var div = document.createElement('div');
            div.innerHTML = str.replace(/<\/?[^>]+>/gi, '');
            return div.childNodes[0] ? div.childNodes[0].nodeValue : '';
        } else {
            return '';
        }
    }
});

/* SVN FILE: $Id: jamal.js 18 2007-06-13 09:07:32Z teemow $ */
/**
 * To quote Dave Cardwell: 
 * Built on the shoulders of giants:
 *   * John Resig      - http://jquery.com/
 *
 * Jamal :  Javascript MVC Assembly Layout <http://jamal-mvc.com/>
 * Copyright (c)    2007, Timo Derstappen <http://teemow.com/>
 *
 * Licensed under The MIT License
 * Redistributions of files must retain the above copyright notice.
 *
 * @filesource
 * @copyright        Copyright (c) 2007, Timo Derstappen
 * @link            
 * @package          jamal
 * @subpackage       jamal.modal
 * @since            Jamal v 0.4
 * @version          $Revision: 28 $
 * @modifiedby       $LastChangedBy: teemow $
 * @lastmodified     $Date: 2007-07-03 23:00:09 +0200 (Di, 03 Jul 2007) $
 * @license          http://www.opensource.org/licenses/mit-license.php The MIT License
 */

/**
 * Jamal modal component
 *
 * This component offers a modal dialog for jamal applications. eg. if your
 * session timed out you can display a modal login form
 *
 * @public
 * @name jamal
 * @cat modal
 */
jamal.fn.extend({
	/**
     * Create a modal dialog
	 *
	 * @example jamal.modal('<h1>message</h1>');
	 *
	 * @private
	 * @name modal
	 * @type jamal
     * @param String content Content that will be displayed in the modal window
	 * @cat modal
	 */
    modal: function(content) {
        if (content) {
            if (!jamal.modal.active) {
                // deactivate screen
                jQuery('body').css('overflow', 'hidden');
                if (jQuery.browser.msie) {
                    jQuery('select').hide();
                }
                jQuery('#wrapper')
                         .after('<div id="jamal_overlay"></div>')
                         .prepend('<div id="jamal_modal"><div class="jamal_size">'+content+'</div></div>');
                
                jamal.modal.active = true;
            } else {
                jQuery('div.jamal_size').html(content);
            }
            jamal.modal.resize();
            return true;
        } else {
            return false;
        }
    }
});

jamal.fn.extend(jamal.fn.modal, {
	/**
	 * Flag for modal dialog
	 *
	 * @public
	 * @property
	 * @name jamal.modal.active
	 * @type Boolean
	 * @cat modal
	 */
    active: false,

	/**
     * Resize the current modal dialog
	 *
	 * @example jamal.modal.resize();
	 *
	 * @private
	 * @name resize
	 * @type jamal
	 * @cat modal
	 */
    resize: function() {
        // width
        jQuery('#jamal_modal').css('width', jQuery('div.jamal_size').width()+'px');
        
        var body = jQuery('#wrapper').width();
        var modal = jQuery('#jamal_modal').width();
        jQuery('#jamal_modal').css('margin-left', (body/2-modal/2)+'px');
        
        // height
        jQuery('#jamal_modal').css('height', jQuery('div.jamal_size').height()+'px');
        if (jQuery.browser.msie) {
            var offset = document.documentElement.scrollTop;
            body = document.documentElement.clientHeight;
        } else {
            var offset = window.pageYOffset;
            body = window.innerHeight;
        }
        modal = jQuery('#jamal_modal').height();
        jQuery('#jamal_modal').css('margin-top', (offset + body/2 - modal/2) +'px');
    },
    
	/**
     * Close the current dialog
	 *
	 * @example jamal.close();
	 *
	 * @private
	 * @name close
	 * @type jamal
	 * @cat modal
	 */
    close: function() {
        if (jamal.modal.active) {
            jQuery('#jamal_modal').fadeOut('slow');
            jQuery('#jamal_overlay').remove();
            jQuery('body').css('overflow', 'auto');
            if (jQuery.browser.msie) {
                jQuery('select').show();
            }
            jamal.modal.active = false;
        }
    }
});
/* SVN FILE: $Id: jamal.js 18 2007-06-13 09:07:32Z teemow $ */
/**
 * To quote Dave Cardwell: 
 * Built on the shoulders of giants:
 *   * John Resig      - http://jquery.com/
 *
 * Jamal :  Javascript MVC Assembly Layout <http://jamal-mvc.com/>
 * Copyright (c)    2007, Timo Derstappen <http://teemow.com/>
 *
 * Licensed under The MIT License
 * Redistributions of files must retain the above copyright notice.
 *
 * @filesource
 * @copyright        Copyright (c) 2007, Timo Derstappen
 * @link            
 * @package          jamal
 * @subpackage       jamal.session
 * @since            Jamal v 0.4
 * @version          $Revision: 28 $
 * @modifiedby       $LastChangedBy: teemow $
 * @lastmodified     $Date: 2007-07-03 23:00:09 +0200 (Di, 03 Jul 2007) $
 * @license          http://www.opensource.org/licenses/mit-license.php The MIT License
 */

/**
 * Jamal session component
 *
 * This requires the modal component.
 *
 * @public
 * @name jamal
 * @cat session
 */
jamal.fn.extend({
    /* Constructor */
	/**
	 * Start the jamal session handling
	 *
     * @example jamal.session();
     * @desc This starts the jamal session handling
	 * @public
	 * @name session
	 * @type Object
	 * @cat session
	 */
     session: function(){
         if(this.config.session) {
             this.log('Session activated');
             this.session.active = true;
             this.session.since = 0;
             this.session.check();
             
             return true;
         }
         return false;
     }
}); 

jamal.fn.extend(jamal.fn.session, {
	/**
	 * Flag for server-side session check
	 *
	 * @public
	 * @property
	 * @name jamal.session.active
	 * @type Boolean
	 * @cat session
	 */
    active: false,
    
	/**
	 * Minutes a session lasts. After this time the user gets logged out and
     * is warned by a notification.
	 *
	 * @public
	 * @property
	 * @name jamal.session.timeout
	 * @type Number
	 * @cat session
	 */
    timeout: 30, // minutes

	/**
	 * Seconds between server-side session checks.
	 *
	 * @public
	 * @property
	 * @name jamal.session.freq
	 * @type Number
	 * @cat session
	 */
    freq: 60, // seconds

	/**
	 * This holds the time of the current session.
	 *
	 * @private
	 * @property
	 * @name jamal.session.since
	 * @type Number
	 * @cat session
	 */
    since: 0, // minutes
    
	/**
     * Check the session on the server side
	 *
	 * @example jamal.session.check();
	 *
	 * @private
	 * @name jamal.session.check
	 * @type Function
	 * @cat session
	 */
    check: function() {
        if (this.since < this.timeout) {
            if (this.since > 0) {
                jamal.current.m.json('/session/');
            }
            this.since += this.freq/60;
            window.setTimeout("jamal.session.check();", this.freq*1000);
        } else {
            this.destroy();
        }
    },
    
	/**
     * Kills a jamal session
     *
     * Calls the url /logout/ to kill the session on the server side. It is 
     * expected to get back some information to be displayed in a modal dialog
	 *
	 * @example jamal.session.destroy();
     * @desc Destroys the current session 
	 *
	 * @private
	 * @name destroy
	 * @type Function
	 * @cat session
	 */
    destroy: function() {
        jamal.current.m.json('/logout/', function(response) {
            jamal.session.active = false;
            jamal.log('Session killed');
            
            if (response.redirect) {
                jamal.session.redirect();
            }
            
            jamal.modal(response.content);
            jamal.session._callback();
        });
    },
    
    /**
     * Empty callback to customize the login screen handling
     *
     * @example jamal.session.callback = function() {
     *     $('#login').focus();
     *     $('form').form(function(response) {
     *         alert('logged in');
     *     });
     * };
     * 
     * @public
     * @name jamal.session.callback
     * @type Function
     * @cat session
     */
    _callback: function() {
        return;
    },
     
    /**
     * Reloads the current page
     *
     * @example jamal.session.reload();
     * @desc Loads the current page again (full request, no xhr)
     *
     * @public
     * @name reload
     * @type jamal
     * @cat session
     */
    reload: function() {
        window.location.replace(window.location.href);
    },
    
    /**
     * The session callback for the server communication
     *
     * If the server reports a session timeout jamal reloads the current
     * page.
     *
     * Jamal expects a JSON response like 
     * { 
     *   session: false,
     *   data: {}
     * }
     *
     * @example jamal.callback(response, 
     *   function(response){
     *     jamal.dir(response.data)
     *   });
     *
     * @public
     * @name _callback
     * @type session
     * @param Object response JSON response from the server.
     * @param Function callback A function to be executed whenever the data is loaded.
     * @cat model
     */
    callback: function(response, callback){
        if (!response.session) {
            // session timeout
            this.reload();
        } else {
            this.reset();
            if (callback) {
                callback(response);
            }
        }
    },

	/**
     * Reset jamal's session timer
	 *
	 * @example jamal.reset();
	 *
	 * @private
	 * @name reset
	 * @type jamal
	 * @cat session
	 */
    reset: function() {
        this.since = 0;
    }    
});

/* SVN FILE: $Id: startup.js 28 2007-07-03 21:00:09Z teemow $ */
/**
 * This file handles the jamal object creation when the dom has finished.
 *
 * jQuery is required
 *
 * Jamal :  Javascript MVC Assembly Layout <http://jamal.moagil.de/>
 * Copyright (c)    2006, Timo Derstappen <http://teemow.com/>
 *
 * Licensed under The MIT License
 * Redistributions of files must retain the above copyright notice.
 *
 * @filesource
 * @copyright        Copyright (c) 2006, Timo Derstappen
 * @link            
 * @package          jamal
 * @subpackage       jamal.startup
 * @since            Jamal v 0.1
 * @version          $Revision: 28 $
 * @modifiedby       $LastChangedBy: teemow $
 * @lastmodified     $Date: 2007-07-03 23:00:09 +0200 (Di, 03 Jul 2007) $
 * @license          http://www.opensource.org/licenses/mit-license.php The MIT License
 */

/**
 * Jamal startup
 *
 * Mapping jamal to the shorter $j namespace and register the onload event to
 * create an instance of jamal.
 *
 * This file sets the document.ready() function which is jQuery's replacement 
 * for the windows.onload event. 
 */

// Map over the $j in case of overwrite
if (typeof $j != "undefined") {
    jamal._$j = $j;
}

// Map the jamal namespace to '$j'
var $j = jamal;

/**
 * Window onload replacement of jquery
 *
 */
$(function(){
    $j = jamal = jamal();
    $j.start();
});
/* SVN FILE: $Id: plugins.js 2378 2007-02-26 14:56:56Z teemow $ */
/**
 * Metadata - jQuery plugin for parsing metadata from elements
 *
 * Copyright (c) 2006 John Resig, Yehuda Katz, Jörn Zaefferer
 *
 * Dual licensed under the MIT and GPL licenses:
 *   http://www.opensource.org/licenses/mit-license.php
 *   http://www.gnu.org/licenses/gpl.html
 *
 * Revision: $Id: plugins.js 2378 2007-02-26 14:56:56Z teemow $
 *
 */

/**
 * Sets the type of metadata to use. Metadata is encoded in JSON, and each property
 * in the JSON will become a property of the element itself.
 *
 * There are three supported types of metadata storage:
 *
 *   attr:  Inside an attribute. The name parameter indicates *which* attribute.
 *          
 *   class: Inside the class attribute, wrapped in curly braces: { }
 *   
 *   elem:  Inside a child element (e.g. a script tag). The
 *          name parameter indicates *which* element.
 *          
 * The metadata for an element is loaded the first time the element is accessed via jQuery.
 *
 * As a result, you can define the metadata type, use $(expr) to load the metadata into the elements
 * matched by expr, then redefine the metadata type and run another $(expr) for other elements.
 * 
 * @name $.meta.setType
 *
 * @example <p id="one" class="some_class {item_id: 1, item_label: 'Label'}">This is a p</p>
 * @before $.meta.setType("class")
 * @after $("#one").data().item_id == 1; $("#one")[0].item_label == "Label"
 * @desc Reads metadata from the class attribute
 * 
 * @example <p id="one" class="some_class" data="{item_id: 1, item_label: 'Label'}">This is a p</p>
 * @before $.meta.setType("attr", "data")
 * @after $("#one").data().item_id == 1; $("#one")[0].item_label == "Label"
 * @desc Reads metadata from a "data" attribute
 * 
 * @example <p id="one" class="some_class"><script>{item_id: 1, item_label: 'Label'}</script>This is a p</p>
 * @before $.meta.setType("elem", "script")
 * @after $("#one").data().item_id == 1; $("#one")[0].item_label == "Label"
 * @desc Reads metadata from a nested script element
 * 
 * @param String type The encoding type
 * @param String name The name of the attribute to be used to get metadata (optional)
 * @cat Plugins/Metadata
 * @descr Sets the type of encoding to be used when loading metadata for the first time
 * @type undefined
 * @see data()
 */

(function($) {
    // settings
    $.meta = {
      type: "class",
      name: "data",
      setType: function(type,name){
        this.type = type;
        this.name = name;
      },
      cre: /({.*})/,
      single: 'data'
    };
    
    // reference to original setArray()
    var setArray = $.fn.setArray;
    
    // define new setArray()
    $.fn.setArray = function(arr){
        return setArray.apply( this, arguments ).each(function(){
          if ( this.metaDone ) return;
          
          var data = "{}";
          
          if ( $.meta.type == "class" ) {
            var m = $.meta.cre.exec( this.className );
            if ( m )
              data = m[1];
          } else if ( $.meta.type == "elem" ) {
              if( !this.getElementsByTagName ) return;
            var e = this.getElementsByTagName($.meta.name);
            if ( e.length )
              data = $.trim(e[0].innerHTML);
          } else if ( this.getAttribute != undefined ) {
            var attr = this.getAttribute( $.meta.name );
            if ( attr )
              data = attr;
          }
          
          if ( !/^{/.test( data ) )
            data = "{" + data + "}";
    
          eval("data = " + data);
    
          if ( $.meta.single )
            this[ $.meta.single ] = data;
          else
            $.extend( this, data );
          
          this.metaDone = true;
        });
    };
    
    /**
     * Returns the metadata object for the first member of the jQuery object.
     *
     * @name data
     * @descr Returns element's metadata object
     * @type jQuery
     * @cat Plugins/Metadata
     */
    $.fn.data = function(){
      return this[0][$.meta.single || "data"];
    };
})(jQuery);

/**
 * Tabs - jQuery plugin for accessible, unobtrusive tabs
 * @requires jQuery v1.0.3
 *
 * http://stilbuero.de/tabs/
 *
 * Copyright (c) 2006 Klaus Hartl (stilbuero.de)
 * Dual licensed under the MIT and GPL licenses:
 * http://www.opensource.org/licenses/mit-license.php
 * http://www.gnu.org/licenses/gpl.html
 *
 * Version: 2.7.4
 */

(function($) { // block scope

$.extend({
    tabs: {
        remoteCount: 0 // TODO in Tabs 3 this is going to be more cleanly in one single namespace
    }
});

/**
 * Create an accessible, unobtrusive tab interface based on a particular HTML structure.
 *
 * The underlying HTML has to look like this:
 *
 * <div id="container">
 *     <ul>
 *         <li><a href="#fragment-1">Section 1</a></li>
 *         <li><a href="#fragment-2">Section 2</a></li>
 *         <li><a href="#fragment-3">Section 3</a></li>
 *     </ul>
 *     <div id="fragment-1">
 *
 *     </div>
 *     <div id="fragment-2">
 *
 *     </div>
 *     <div id="fragment-3">
 *
 *     </div>
 * </div>
 *
 * Each anchor in the unordered list points directly to a section below represented by one of the
 * divs (the URI in the anchor's href attribute refers to the fragment with the corresponding id).
 * Because such HTML structure is fully functional on its own, e.g. without JavaScript, the tab
 * interface is accessible and unobtrusive.
 *
 * A tab is also bookmarkable via hash in the URL. Use the History/Remote plugin (Tabs will
 * auto-detect its presence) to fix the back (and forward) button.
 *
 * @example $('#container').tabs();
 * @desc Create a basic tab interface.
 * @example $('#container').tabs(2);
 * @desc Create a basic tab interface with the second tab initially activated.
 * @example $('#container').tabs({disabled: [3, 4]});
 * @desc Create a tab interface with the third and fourth tab being disabled.
 * @example $('#container').tabs({fxSlide: true});
 * @desc Create a tab interface that uses slide down/up animations for showing/hiding tab
 *       content upon tab switching.
 *
 * @param Number initial An integer specifying the position of the tab (no zero-based index) that
 *                       gets activated at first (on page load). Two alternative ways to specify
 *                       the active tab will overrule this argument. First a li element
 *                       (representing one single tab) belonging to the selected tab class, e.g.
 *                       set the selected tab class (default: "tabs-selected", see option
 *                       selectedClass) for one of the unordered li elements in the HTML source.
 *                       In addition if a fragment identifier/hash in the URL of the page refers
 *                       to the id of a tab container of a tab interface the corresponding tab will
 *                       be activated and both the initial argument as well as an eventually
 *                       declared class attribute will be overruled. Defaults to 1 if omitted.
 * @param Object settings An object literal containing key/value pairs to provide optional settings.
 * @option Array<Number> disabled An array containing the position of the tabs (no zero-based index)
 *                                that should be disabled on initialization. Default value: null.
 *                                A tab can also be disabled by simply adding the disabling class
 *                                (default: "tabs-disabled", see option disabledClass) to the li
 *                                element representing that particular tab.
 * @option Boolean bookmarkable Boolean flag indicating if support for bookmarking and history (via
 *                              changing hash in the URL of the browser) is enabled. Default value:
 *                              false, unless the History/Remote plugin is included. In that case the
 *                              default value becomes true. @see $.ajaxHistory.initialize
 * @option Boolean remote Boolean flag indicating that tab content has to be loaded remotely from
 *                        the url given in the href attribute of the tab menu anchor elements.
 * @option String spinner The content of this string is shown in a tab while remote content is loading.
 *                        Insert plain text as well as an img here. To turn off this notification
 *                        pass an empty string or null. Default: "Loading&#8230;".
 * @option String hashPrefix A String that is used for constructing the hash the link's href attribute
 *                           of a remote tab gets altered to, such as "#remote-1".
 *                           Default value: "remote-tab-".
 * @option Boolean fxFade Boolean flag indicating whether fade in/out animations are used for tab
 *                        switching. Can be combined with fxSlide. Will overrule fxShow/fxHide.
 *                        Default value: false.
 * @option Boolean fxSlide Boolean flag indicating whether slide down/up animations are used for tab
 *                         switching. Can be combined with fxFade. Will overrule fxShow/fxHide.
 *                         Default value: false.
 * @option String|Number fxSpeed A string representing one of the three predefined speeds ("slow",
 *                               "normal", or "fast") or the number of milliseconds (e.g. 1000) to
 *                               run an animation. Default value: "normal".
 * @option Object fxShow An object literal of the form jQuery's animate function expects for making
 *                       your own, custom animation to reveal a tab upon tab switch. Unlike fxFade
 *                       or fxSlide this animation is independent from an optional hide animation.
 *                       Default value: null. @see animate
 * @option Object fxHide An object literal of the form jQuery's animate function expects for making
 *                       your own, custom animation to hide a tab upon tab switch. Unlike fxFade
 *                       or fxSlide this animation is independent from an optional show animation.
 *                       Default value: null. @see animate
 * @option String|Number fxShowSpeed A string representing one of the three predefined speeds
 *                                   ("slow", "normal", or "fast") or the number of milliseconds
 *                                   (e.g. 1000) to run the animation specified in fxShow.
 *                                   Default value: fxSpeed.
 * @option String|Number fxHideSpeed A string representing one of the three predefined speeds
 *                                   ("slow", "normal", or "fast") or the number of milliseconds
 *                                   (e.g. 1000) to run the animation specified in fxHide.
 *                                   Default value: fxSpeed.
 * @option Boolean fxAutoHeight Boolean flag that if set to true causes all tab heights
 *                              to be constant (being the height of the tallest tab).
 *                              Default value: false.
 * @option Function onClick A function to be invoked upon tab switch, immediatly after a tab has
 *                          been clicked, e.g. before the other's tab content gets hidden. The
 *                          function gets passed three arguments: the first one is the clicked
 *                          tab (e.g. an anchor element), the second one is the DOM element
 *                          containing the content of the clicked tab (e.g. the div), the third
 *                          argument is the one of the tab that gets hidden. If this callback
 *                          returns false, the tab switch is canceled (use to disallow tab
 *                          switching for the reason of a failed form validation for example).
 *                          Default value: null.
 * @option Function onHide A function to be invoked upon tab switch, immediatly after one tab's
 *                         content got hidden (with or without an animation) and right before the
 *                         next tab is revealed. The function gets passed three arguments: the
 *                         first one is the clicked tab (e.g. an anchor element), the second one
 *                         is the DOM element containing the content of the clicked tab, (e.g. the
 *                         div), the third argument is the one of the tab that gets hidden.
 *                         Default value: null.
 * @option Function onShow A function to be invoked upon tab switch. This function is invoked
 *                         after the new tab has been revealed, e.g. after the switch is completed.
 *                         The function gets passed three arguments: the first one is the clicked
 *                         tab (e.g. an anchor element), the second one is the DOM element
 *                         containing the content of the clicked tab, (e.g. the div), the third
 *                         argument is the one of the tab that gets hidden. Default value: null.
 * @option String navClass A CSS class that is used to identify the tabs unordered list by class if
 *                         the required HTML structure differs from the default one.
 *                         Default value: "tabs-nav".
 * @option String selectedClass The CSS class attached to the li element representing the
 *                              currently selected (active) tab. Default value: "tabs-selected".
 * @option String disabledClass The CSS class attached to the li element representing a disabled
 *                              tab. Default value: "tabs-disabled".
 * @option String containerClass A CSS class that is used to identify tab containers by class if
 *                               the required HTML structure differs from the default one.
 *                               Default value: "tabs-container".
 * @option String hideClass The CSS class used for hiding inactive tabs. A class is used instead
 *                          of "display: none" in the style attribute to maintain control over
 *                          visibility in other media types than screen, most notably print.
 *                          Default value: "tabs-hide".
 * @option String loadingClass The CSS class used for indicating that an Ajax tab is currently
 *                             loading, for example by showing a spinner.
 *                             Default value: "tabs-loading".
 * @option String tabStruct @deprecated A CSS selector or basic XPath expression reflecting a
 *                          nested HTML structure that is different from the default single div
 *                          structure (one div with an id inside the overall container holds one
 *                          tab's content). If for instance an additional div is required to wrap
 *                          up the several tab containers such a structure is expressed by "div>div".
 *                          Default value: "div".
 * @type jQuery
 *
 * @name tabs
 * @cat Plugins/Tabs
 * @author Klaus Hartl/klaus.hartl@stilbuero.de
 */
$.fn.tabs = function(initial, settings) {

    // settings
    if (typeof initial == 'object') settings = initial; // no initial tab given but a settings object
    settings = $.extend({
        initial: (initial && typeof initial == 'number' && initial > 0) ? --initial : 0,
        disabled: null,
        bookmarkable: $.ajaxHistory ? true : false,
        remote: false,
        spinner: 'Loading&#8230;',
        hashPrefix: 'remote-tab-',
        fxFade: null,
        fxSlide: null,
        fxShow: null,
        fxHide: null,
        fxSpeed: 'normal',
        fxShowSpeed: null,
        fxHideSpeed: null,
        fxAutoHeight: false,
        onClick: null,
        onHide: null,
        onShow: null,
        navClass: 'tabs-nav',
        selectedClass: 'tabs-selected',
        disabledClass: 'tabs-disabled',
        containerClass: 'tabs-container',
        hideClass: 'tabs-hide',
        loadingClass: 'tabs-loading',
        tabStruct: 'div'
    }, settings || {});

    $.browser.msie6 = $.browser.msie && ($.browser.version && $.browser.version < 7 || /6.0/.test(navigator.userAgent));    

    // helper to prevent scroll to fragment
    function unFocus() {
        scrollTo(0, 0);
    }

    // initialize tabs
    return this.each(function() {

        // remember wrapper for later
        var container = this;

        // setup nav
        var nav = $('ul.' + settings.navClass, container);
        nav = nav.size() && nav || $('>ul:eq(0)', container); // fallback to default structure
        var tabs = $('a', nav);

        // prepare remote tabs
        if (settings.remote) {
            tabs.each(function() {
                var id = settings.hashPrefix + (++$.tabs.remoteCount), hash = '#' + id, url = this.href;
                this.href = hash;
                $('<div id="' + id + '" class="' + settings.containerClass + '"></div>').appendTo(container);

                $(this).bind('loadRemoteTab', function(e, callback) {
                    var $$ = $(this).addClass(settings.loadingClass), span = $('span', this)[0], tabTitle = span.innerHTML;
                    if (settings.spinner) {
                        // TODO if spinner is image
                        span.innerHTML = '<em>' + settings.spinner + '</em>'; // WARNING: html(...) crashes Safari with jQuery 1.1.2
                    }
                    setTimeout(function() { // Timeout is again required in IE, "wait" for id being restored
                        $(hash).load(url, function() {
                            if (settings.spinner) {
                                span.innerHTML = tabTitle; // WARNING: html(...) crashes Safari with jQuery 1.1.2
                            }
                            $$.removeClass(settings.loadingClass);
                            callback && callback();
                        });
                    }, 0);
                });

            });
        }

        // set up containers
        var containers = $('div.' + settings.containerClass, container);
        containers = containers.size() && containers || $('>' + settings.tabStruct, container); // fallback to default structure

        // attach classes for styling if not present
        nav.is('.' + settings.navClass) || nav.addClass(settings.navClass);
        containers.each(function() {
            var $$ = $(this);
            $$.is('.' + settings.containerClass) || $$.addClass(settings.containerClass);
        });

        // try to retrieve active tab from class in HTML
        var hasSelectedClass = $('li', nav).index( $('li.' + settings.selectedClass, nav)[0] );
        if (hasSelectedClass >= 0) {
           settings.initial = hasSelectedClass;
        }

        // try to retrieve active tab from hash in url, will override class in HTML
        if (location.hash) {
            tabs.each(function(i) {
                if (this.hash == location.hash) {
                    settings.initial = i;
                    // prevent page scroll to fragment
                    if (($.browser.msie || $.browser.opera) && !settings.remote) {
                        var toShow = $(location.hash);
                        var toShowId = toShow.attr('id');
                        toShow.attr('id', '');
                        setTimeout(function() {
                            toShow.attr('id', toShowId); // restore id
                        }, 500);
                    }
                    unFocus();
                    return false; // break
                }
            });
        }
        if ($.browser.msie) {
            unFocus(); // fix IE focussing bottom of the page for some unknown reason
        }

        // highlight tab accordingly
        containers.filter(':eq(' + settings.initial + ')').show().end().not(':eq(' + settings.initial + ')').addClass(settings.hideClass);
        $('li', nav).removeClass(settings.selectedClass).eq(settings.initial).addClass(settings.selectedClass); // we need to remove classes eventually if hash takes precedence over class
        // trigger load of initial tab
        tabs.eq(settings.initial).trigger('loadRemoteTab').end();

        // setup auto height
        if (settings.fxAutoHeight) {
            // helper
            var _setAutoHeight = function(reset) {
                // get tab heights in top to bottom ordered array
                var heights = $.map(containers.get(), function(el) {
                    var h, jq = $(el);
                    if (reset) {
                        if ($.browser.msie6) {
                            el.style.removeExpression('behaviour');
                            el.style.height = '';
                            el.minHeight = null;
                        }
                        h = jq.css({'min-height': ''}).height(); // use jQuery's height() to get hidden element values
                    } else {
                        h = jq.height(); // use jQuery's height() to get hidden element values
                    }
                    return h;
                }).sort(function(a, b) {
                    return b - a;
                });
                if ($.browser.msie6) {
                    containers.each(function() {
                        this.minHeight = heights[0] + 'px';
                        this.style.setExpression('behaviour', 'this.style.height = this.minHeight ? this.minHeight : "1px"'); // using an expression to not make print styles useless
                    });
                } else {
                    containers.css({'min-height': heights[0] + 'px'});
                }
            };
            // call once for initialization
            _setAutoHeight();
            // trigger auto height adjustment if needed
            var cachedWidth = container.offsetWidth;
            var cachedHeight = container.offsetHeight;
            var watchFontSize = $('#tabs-watch-font-size').get(0) || $('<span id="tabs-watch-font-size">M</span>').css({display: 'block', position: 'absolute', visibility: 'hidden'}).appendTo(document.body).get(0);
            var cachedFontSize = watchFontSize.offsetHeight;
            setInterval(function() {
                var currentWidth = container.offsetWidth;
                var currentHeight = container.offsetHeight;
                var currentFontSize = watchFontSize.offsetHeight;
                if (currentHeight > cachedHeight || currentWidth != cachedWidth || currentFontSize != cachedFontSize) {
                    _setAutoHeight((currentWidth > cachedWidth || currentFontSize < cachedFontSize)); // if heights gets smaller reset min-height
                    cachedWidth = currentWidth;
                    cachedHeight = currentHeight;
                    cachedFontSize = currentFontSize;
                }
            }, 50);
        }

        // setup animations
        var showAnim = {}, hideAnim = {}, showSpeed = settings.fxShowSpeed || settings.fxSpeed, hideSpeed = settings.fxHideSpeed || settings.fxSpeed;
        if (settings.fxSlide || settings.fxFade) {
            if (settings.fxSlide) {
                showAnim['height'] = 'show';
                hideAnim['height'] = 'hide';
            }
            if (settings.fxFade) {
                showAnim['opacity'] = 'show';
                hideAnim['opacity'] = 'hide';
            }
        } else {
            if (settings.fxShow) {
                showAnim = settings.fxShow;
            } else { // use some kind of animation to prevent browser scrolling to the tab
                showAnim['min-width'] = 0; // avoid opacity, causes flicker in Firefox
                showSpeed = 1; // as little as 1 is sufficient
            }
            if (settings.fxHide) {
                hideAnim = settings.fxHide;
            } else { // use some kind of animation to prevent browser scrolling to the tab
                hideAnim['min-width'] = 0; // avoid opacity, causes flicker in Firefox
                hideSpeed = 1; // as little as 1 is sufficient
            }
        }

        // callbacks
        var onClick = settings.onClick, onHide = settings.onHide, onShow = settings.onShow;

        // attach activateTab event, required for activating a tab programmatically
        tabs.bind('triggerTab', function() {

            // if the tab is already selected or disabled or animation is still running stop here
            var li = $(this).parents('li:eq(0)');
            if (container.locked || li.is('.' + settings.selectedClass) || li.is('.' + settings.disabledClass)) {
                return false;
            }

            var hash = this.hash;

            if ($.browser.msie) {

                $(this).trigger('click');
                if (settings.bookmarkable) {
                    $.ajaxHistory.update(hash);
                    location.hash = hash.replace('#', '');
                }

            } else if ($.browser.safari) {

                // Simply setting location.hash puts Safari into the eternal load state... ugh! Submit a form instead.
                var tempForm = $('<form action="' + hash + '"><div><input type="submit" value="h" /></div></form>').get(0); // no need to append it to the body
                tempForm.submit(); // does not trigger the form's submit event...
                $(this).trigger('click'); // ...thus do stuff here
                if (settings.bookmarkable) {
                    $.ajaxHistory.update(hash);
                }

            } else {

                if (settings.bookmarkable) {
                    location.hash = hash.replace('#', '');
                } else {
                    $(this).trigger('click');
                }

            }

        });

        // attach disable event, required for disabling a tab
        tabs.bind('disableTab', function() {
            var li = $(this).parents('li:eq(0)');
            if ($.browser.safari) { /* fix opacity of tab after disabling in Safari... */
                li.animate({ opacity: 0 }, 1, function() {
                   li.css({opacity: ''});
                });
            }
            li.addClass(settings.disabledClass);

        });

        // disabled from settings
        if (settings.disabled && settings.disabled.length) {
            for (var i = 0, k = settings.disabled.length; i < k; i++) {
                tabs.eq(--settings.disabled[i]).trigger('disableTab').end();
            }
        };

        // attach enable event, required for reenabling a tab
        tabs.bind('enableTab', function() {
            var li = $(this).parents('li:eq(0)');
            li.removeClass(settings.disabledClass);
            if ($.browser.safari) { /* fix disappearing tab after enabling in Safari... */
                li.animate({ opacity: 1 }, 1, function() {
                    li.css({opacity: ''});
                });
            }
        });

        // attach click event
        tabs.bind('click', function(e) {

            var trueClick = e.clientX; // add to history only if true click occured, not a triggered click
            var clicked = this, li = $(this).parents('li:eq(0)'), toShow = $(this.hash), toHide = containers.filter(':visible');

            // if animation is still running, tab is selected or disabled or onClick callback returns false stop here
            // check if onClick returns false last so that it is not executed for a disabled tab
            if (container['locked'] || li.is('.' + settings.selectedClass) || li.is('.' + settings.disabledClass) || typeof onClick == 'function' && onClick(this, toShow[0], toHide[0]) === false) {
                this.blur();
                return false;
            }

            container['locked'] = true;

            // show new tab
            if (toShow.size()) {

                // prevent scrollbar scrolling to 0 and than back in IE7, happens only if bookmarking/history is enabled
                if ($.browser.msie && settings.bookmarkable) {
                    var toShowId = this.hash.replace('#', '');
                    toShow.attr('id', '');
                    setTimeout(function() {
                        toShow.attr('id', toShowId); // restore id
                    }, 0);
                }

                var resetCSS = { display: '', overflow: '', height: '' };
                if (!$.browser.msie) { // not in IE to prevent ClearType font issue
                    resetCSS['opacity'] = '';
                }
                
                // switch tab, animation prevents browser scrolling to the fragment
                function switchTab() {
                    if (settings.bookmarkable && trueClick) { // add to history only if true click occured, not a triggered click
                        $.ajaxHistory.update(clicked.hash);
                    }
                    toHide.animate(hideAnim, hideSpeed, function() { //
                        $(clicked).parents('li:eq(0)').addClass(settings.selectedClass).siblings().removeClass(settings.selectedClass);
                        toHide.addClass(settings.hideClass).css(resetCSS); // maintain flexible height and accessibility in print etc.                        
                        if (typeof onHide == 'function') {
                            onHide(clicked, toShow[0], toHide[0]);
                        }
                        if (!(settings.fxSlide || settings.fxFade || settings.fxShow)) {
                            toShow.css('display', 'block'); // prevent occasionally occuring flicker in Firefox cause by gap between showing and hiding the tab containers
                        }
                        toShow.animate(showAnim, showSpeed, function() {
                            toShow.removeClass(settings.hideClass).css(resetCSS); // maintain flexible height and accessibility in print etc.
                            if ($.browser.msie) {
                                toHide[0].style.filter = '';
                                toShow[0].style.filter = '';
                            }
                            if (typeof onShow == 'function') {
                                onShow(clicked, toShow[0], toHide[0]);
                            }
                            container['locked'] = null;
                        });
                    });
                }

                if (!settings.remote) {
                    switchTab();
                } else {
                    $(clicked).trigger('loadRemoteTab', [switchTab]);
                }

            } else {
                alert('There is no such container.');
            }

            // Set scrollbar to saved position - need to use timeout with 0 to prevent browser scroll to target of hash
            var scrollX = window.pageXOffset || document.documentElement && document.documentElement.scrollLeft || document.body.scrollLeft || 0;
            var scrollY = window.pageYOffset || document.documentElement && document.documentElement.scrollTop || document.body.scrollTop || 0;
            setTimeout(function() {
                window.scrollTo(scrollX, scrollY);
            }, 0);

            this.blur(); // prevent IE from keeping other link focussed when using the back button

            return settings.bookmarkable && !!trueClick; // convert undefined to Boolean for IE

        });

        // enable history support if bookmarking and history is turned on
        if (settings.bookmarkable) {
            $.ajaxHistory.initialize(function() {
                tabs.eq(settings.initial).trigger('click').end();
            });
        }

    });

};

/**
 * Activate a tab programmatically with the given position (no zero-based index)
 * or its id, e.g. the URL's fragment identifier/hash representing a tab, as if the tab
 * itself were clicked.
 *
 * @example $('#container').triggerTab(2);
 * @desc Activate the second tab of the tab interface contained in <div id="container">.
 * @example $('#container').triggerTab(1);
 * @desc Activate the first tab of the tab interface contained in <div id="container">.
 * @example $('#container').triggerTab();
 * @desc Activate the first tab of the tab interface contained in <div id="container">.
 * @example $('#container').triggerTab('fragment-2');
 * @desc Activate a tab via its URL fragment identifier representation.
 *
 * @param String|Number tab Either a string that matches the id of the tab (the URL's
 *                          fragment identifier/hash representing a tab) or an integer
 *                          specifying the position of the tab (no zero-based index) to
 *                          be activated. If this parameter is omitted, the first tab
 *                          will be activated.
 * @type jQuery
 *
 * @name triggerTab
 * @cat Plugins/Tabs
 * @author Klaus Hartl/klaus.hartl@stilbuero.de
 */

/**
 * Disable a tab, so that clicking it has no effect.
 *
 * @example $('#container').disableTab(2);
 * @desc Disable the second tab of the tab interface contained in <div id="container">.
 *
 * @param String|Number tab Either a string that matches the id of the tab (the URL's
 *                          fragment identifier/hash representing a tab) or an integer
 *                          specifying the position of the tab (no zero-based index) to
 *                          be disabled. If this parameter is omitted, the first tab
 *                          will be disabled.
 * @type jQuery
 *
 * @name disableTab
 * @cat Plugins/Tabs
 * @author Klaus Hartl/klaus.hartl@stilbuero.de
 */

/**
 * Enable a tab that has been disabled.
 *
 * @example $('#container').enableTab(2);
 * @desc Enable the second tab of the tab interface contained in <div id="container">.
 *
 * @param String|Number tab Either a string that matches the id of the tab (the URL's
 *                          fragment identifier/hash representing a tab) or an integer
 *                          specifying the position of the tab (no zero-based index) to
 *                          be enabled. If this parameter is omitted, the first tab
 *                          will be enabled.
 * @type jQuery
 *
 * @name enableTab
 * @cat Plugins/Tabs
 * @author Klaus Hartl/klaus.hartl@stilbuero.de
 */

var tabEvents = ['triggerTab', 'disableTab', 'enableTab'];
for (var i = 0; i < tabEvents.length; i++) {
    $.fn[tabEvents[i]] = (function(tabEvent) {
        return function(tab) {
            return this.each(function() {
                var nav = $('ul.tabs-nav' , this);
                nav = nav.size() && nav || $('>ul:eq(0)', this); // fallback to default structure
                var a;
                if (!tab || typeof tab == 'number') {
                    a = $('li a', nav).eq((tab && tab > 0 && tab - 1 || 0)); // fall back to 0
                } else if (typeof tab == 'string') {
                    a = $('li a[@href$="#' + tab + '"]', nav);
                }
                a.trigger(tabEvent);
            });
        };
    })(tabEvents[i]);
}

/**
 * Get the position of the currently selected tab (no zero-based index).
 *
 * @example $('#container').activeTab();
 * @desc Get the position of the currently selected tab of an interface
 * contained in <div id="container">.
 *
 * @type Number
 *
 * @name activeTab
 * @cat Plugins/Tabs
 * @author Klaus Hartl/klaus.hartl@stilbuero.de
 */

$.fn.activeTab = function() {
    var selectedTabs = [];
    this.each(function() {
        var nav = $('ul.tabs-nav' , this);
        nav = nav.size() && nav || $('>ul:eq(0)', this); //fallback to default structure
        var lis = $('li', nav);
        selectedTabs.push(lis.index( lis.filter('.tabs-selected')[0] ) + 1);
    });
    return selectedTabs[0];
};

})(jQuery);}
